﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Drawing;
using System.Text.RegularExpressions;
using System.Configuration;
using System.Data.SqlClient;
using System.Drawing.Imaging;
using System.IO;
using System.Text;
using System.Reflection;
using System.Diagnostics;

namespace blackhouse.Web {
    public abstract class DynamicImage : IHttpHandler {

        #region IHttpHandler Members

        public bool IsReusable {
            get { return false; }
        }

        public void ProcessRequest(HttpContext context) {
            try {
#if DEBUG
                Stopwatch watch = new Stopwatch();
                watch.Start();
#endif
                this.Process(context);
#if DEBUG
                watch.Stop();
                Debug.WriteLine(context.Request.Url + ": " + watch.ElapsedMilliseconds + "ms");
#endif
            } catch (Exception e) {
                context.Response.Clear();
                context.Response.Write(e);
            }
        }

        #endregion

        #region Fields

        private Size boxSize = Size.Empty;
        private bool boxSizeFillInner = false;
        private string eTag = "";
        private string eTagRequest = "";
        private string fileExtenstion = "";
        private string fileNameWithExtension = "";
        protected DateTime lastModified = DateTime.MinValue;

        #endregion

        #region Properties

        /// <summary>
        /// If Image not found return box 1x2 px of this color. If color not set throw error
        /// </summary>
        public Color ColorOfEmptyImage { get; set; }

        #endregion

        #region Methods

        private bool CheckExtension(string extension) {
            extension = extension.ToLower();
            return (extension == "gif" || extension == "jpg" || extension == "png");
        }

        protected virtual bool GetBoxSizeFillInner(Uri url) {
            return false;
        }

        protected virtual Size GetBoxSize(Uri url) {
            Regex findSizeInfo = new Regex(@"(\d*)x(\d*)/");
            Match finded = findSizeInfo.Match(url.ToString());
            if (finded.Success) {
                int width = Int32.Parse(finded.Groups[1].Value);
                int height = Int32.Parse(finded.Groups[2].Value);
                Size size = new Size(width, height);
                return size;
            }
            return Size.Empty;
        }

        protected virtual string GetFileExtension(Uri url) {
            string fExt = url.Segments.Last();
            fExt = fExt.ReplaceEx(".axd", "");
            fExt = fExt.Substring(fExt.LastIndexOf('.') + 1);
            return fExt.ToLower();
        }

        protected virtual string GetFileName(Uri url) {
            int segmentIndex = url.Segments.Length - 2;
            string fileName = url.Segments.GetValue(segmentIndex).ToString().Trim('/');
            return fileName;
        }

        protected virtual string GetFileNameWithExtension(Uri url) {
            return this.GetFileNameWithExtension(this.GetFileName(url), this.GetFileExtension(url));
        }

        protected virtual string GetFileNameWithExtension(string fileName, string fileExtension) {
            return string.Format("{0}.{1}", fileName, fileExtension);
        }

        protected abstract string GetGuid(string fileName);

        public static string GetImageUrl(string icon, Size size) {
            return GetImageUrl(icon, size, "DbImages");
        }

        public static string GetImageUrl(string icon, Size size, string handlerFileName) {
            string[] parts = icon.Split('.');
            if (parts.Length != 2) return "";
            StringBuilder buildUrl = new StringBuilder("/");
            if (!size.IsEmpty)
                buildUrl.AppendFormat("{0}x{1}/", size.Width, size.Height);
            buildUrl.Append(string.Join(".", parts.Take(parts.Length - 1).ToArray()) + "/" + handlerFileName + "." + parts.Last());
            return buildUrl.ToString();
        }

        private bool IsGZipSupported() {
            string acceptEncoding = HttpContext.Current.Request.Headers["Accept-Encoding"];
            if (!string.IsNullOrEmpty(acceptEncoding) &&
                 (acceptEncoding.Contains("gzip") || acceptEncoding.Contains("deflate")))
                return true;
            return false;
        }

        private bool IsModified(string currentETag, string requestETag) {
            if (HttpContext.Current.Request.RawUrl.Contains("nocache=1")) return true;
            //            if (HttpContext.Current.Request["nocache"] == "1") return true;
            return currentETag != requestETag;
        }

        private ImageCodecInfo GetEncoder(ImageFormat format) {
            ImageCodecInfo[] codecs = ImageCodecInfo.GetImageDecoders();
            foreach (ImageCodecInfo codec in codecs) {
                if (codec.FormatID == format.Guid) {
                    return codec;
                }
            }
            return null;
        }

        private Size GetImageSize(Size bitmapSize, Size boxSize, bool fillInner) {
            if (boxSize.IsEmpty) return boxSize;
            if (!fillInner) {
                if (boxSize.Width < bitmapSize.Width || boxSize.Height < bitmapSize.Height) {
                    double resizeValue = 1;
                    double resizeWidth, resizeHeight;
                    resizeHeight = 1d * boxSize.Width / bitmapSize.Width;
                    resizeWidth = 1d * boxSize.Height / bitmapSize.Height;
                    resizeValue = (resizeHeight < resizeWidth) ? resizeHeight : resizeWidth;

                    int width = (int)Math.Floor(bitmapSize.Width * resizeValue);
                    int height = (int)Math.Floor(bitmapSize.Height * resizeValue);
                    return new Size(width, height);
                } else
                    return bitmapSize;
            } else {
                if (bitmapSize.Width > boxSize.Width && bitmapSize.Height > boxSize.Height) {
                    double resizeW, resizeH;
                    resizeW = 1d * boxSize.Width / bitmapSize.Width;
                    resizeH = 1d * boxSize.Height / bitmapSize.Height;
                    int nw, nh;
                    if (resizeW > resizeH) {
                        nw = boxSize.Width;
                        nh = (int)Math.Round(bitmapSize.Height * resizeW);
                    } else {
                        nh = boxSize.Height;
                        nw = (int)Math.Round(bitmapSize.Width * resizeH);
                    }
                    return new Size(nw, nh);
                } else
                    return bitmapSize;
            }
        }

        private Image Resize(Image bitmap, Size box) {
            return this.Resize(bitmap, box, false);
        }

        private Image Resize(Image bitmap, Size box, bool fillInner) {
            Size newImageSize = this.GetImageSize(bitmap.Size, box, fillInner);
            if (!newImageSize.Equals(bitmap.Size)) {
                Image thumb = new Bitmap(newImageSize.Width, newImageSize.Height);
                Graphics gp = Graphics.FromImage(thumb);
                gp.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
                gp.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
                gp.PixelOffsetMode = System.Drawing.Drawing2D.PixelOffsetMode.HighQuality;
                gp.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighQuality;
                gp.DrawImage(bitmap, 0, 0, newImageSize.Width, newImageSize.Height);
                bitmap.Dispose();
                gp.Dispose();
                return thumb;
            } else {
                Image thumb = new Bitmap(bitmap.Width, bitmap.Height);
                Graphics gp = Graphics.FromImage(thumb);
                gp.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
                gp.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
                gp.PixelOffsetMode = System.Drawing.Drawing2D.PixelOffsetMode.HighQuality;
                gp.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighQuality;
                gp.DrawImage(bitmap, 0, 0, bitmap.Width, bitmap.Height);
                bitmap.Dispose();
                gp.Dispose();
                return thumb;
            }
        }

        //private Image Resize(Image bitmap, Size box, bool fillInner) {
        //    Size newImageSize = this.GetImageSize(bitmap.Size, box, fillInner);
        //    if (!box.IsEmpty && (box.Width < bitmap.Width || box.Height < bitmap.Height)) {
        //        double resizeValue = 1;
        //        double resizeWidth, resizeHeight;
        //        resizeHeight = 1d * box.Width / bitmap.Width;
        //        resizeWidth = 1d * box.Height / bitmap.Height;
        //        resizeValue = (resizeHeight < resizeWidth) ? resizeHeight : resizeWidth;

        //        int width = (int)Math.Floor(bitmap.Width * resizeValue);
        //        int height = (int)Math.Floor(bitmap.Height * resizeValue);
        //        Image thumb = new Bitmap(width, height);
        //        Graphics gp = Graphics.FromImage(thumb);
        //        gp.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
        //        gp.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
        //        gp.PixelOffsetMode = System.Drawing.Drawing2D.PixelOffsetMode.HighQuality;
        //        gp.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighQuality;
        //        gp.DrawImage(bitmap, 0, 0, width, height);
        //        bitmap.Dispose();
        //        gp.Dispose();
        //        return thumb;
        //    }
        //    else {
        //        Image thumb = new Bitmap(bitmap.Width, bitmap.Height);
        //        Graphics gp = Graphics.FromImage(thumb);
        //        gp.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
        //        gp.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
        //        gp.PixelOffsetMode = System.Drawing.Drawing2D.PixelOffsetMode.HighQuality;
        //        gp.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighQuality;
        //        gp.DrawImage(bitmap, 0, 0, bitmap.Width, bitmap.Height);
        //        bitmap.Dispose();
        //        return thumb;
        //    }
        //}

        private void AddWatermark(ImageWatermark watermark, Graphics mainPhoto, Size mainPhotoSize) {
            using (Stream watermarkStream = watermark.GetImageStream()) {
                if (watermarkStream != null) {
                    using (Bitmap bp = new Bitmap(watermarkStream)) {
                        Size watermarkSize = new Size();
                        watermarkSize.Width = Convert.ToInt32(System.Math.Ceiling(mainPhotoSize.Width * watermark.PercentWidth / 100d));
                        watermarkSize.Height = Convert.ToInt32(System.Math.Ceiling(mainPhotoSize.Height * watermark.PercentHeight / 100d));
                        using (Image bp2 = this.Resize(bp, watermarkSize)) {
                            Point watermarkPosition = new Point();
                            watermarkPosition.X = Convert.ToInt32(System.Math.Ceiling(mainPhotoSize.Width * watermark.PercentPositionX / 100d));
                            if (watermark.PercentPositionX < 0)
                                watermarkPosition.X = mainPhotoSize.Width + watermarkPosition.X - bp2.Size.Width;
                            watermarkPosition.Y = Convert.ToInt32(System.Math.Ceiling(mainPhotoSize.Height * watermark.PercentPositionY / 100d));
                            if (watermark.PercentPositionY < 0)
                                watermarkPosition.Y = mainPhotoSize.Height + watermarkPosition.Y - bp2.Size.Height;
                            mainPhoto.DrawImage(bp2, watermarkPosition);
                        }
                    }
                }
            }
        }

        protected virtual ImageWatermark[] GetWatermarks() {
            return new ImageWatermark[0];
        }

        private void Process(HttpContext context) {
            try {
                this.fileExtenstion = this.GetFileExtension(context.Request.Url);
                if (!this.CheckExtension(this.fileExtenstion))
                    this.fileExtenstion = this.GetFileNameWithExtension(context.Request.Url).Split('.').Last();
                if (this.CheckExtension(this.fileExtenstion)) {
                    this.fileNameWithExtension = this.GetFileNameWithExtension(context.Request.Url);
                    this.boxSize = this.GetBoxSize(context.Request.Url);
                    this.boxSizeFillInner = this.GetBoxSizeFillInner(context.Request.Url);
                    this.SetTags(this.fileNameWithExtension, this.boxSize, this.boxSizeFillInner, context.Request);
                    this.SetHeadersForAll(context.Response, this.eTag);
                    if (this.IsModified(this.eTag, this.eTagRequest)) {
                        this.SetHeadersForImage(context.Response, this.fileExtenstion);
                        //this.SetGZip();
                        ImageFormat imageFormat = this.GetImageFormat(this.fileExtenstion);
                        Image bitmap = this.ReadImage(this.fileNameWithExtension);
                        if (bitmap == null) {
                            this.SetHeadersFor404NotFound(context.Response);
                            //if (this.ColorOfEmptyImage == null)
                            //    throw new DbImagesNotFoundException();
                            //else {
                            //    bitmap = new Bitmap(1, 1);
                            //    Graphics gph = Graphics.FromImage(bitmap);
                            //    gph.Clear(this.ColorOfEmptyImage);
                            //    gph.Flush();
                            //}
                        }
                        Image image = this.Resize(bitmap, this.boxSize, this.boxSizeFillInner);
                        Graphics gp = Graphics.FromImage(image);
                        gp.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
                        gp.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
                        gp.PixelOffsetMode = System.Drawing.Drawing2D.PixelOffsetMode.HighQuality;
                        gp.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighQuality;
                        foreach (ImageWatermark watermark in this.GetWatermarks()) {
                            this.AddWatermark(watermark, gp, image.Size);
                        }
                        this.AcceptModification(image, gp);
                        this.WriteImageToResponse(image, imageFormat, context.Response);
                        bitmap.Dispose();
                        image.Dispose();
                        gp.Dispose();
                    } else
                        this.SetIsNotModified(context.Response);

                }
            } catch (Exception e) {
                HttpContext.Current.Response.ContentType = "text/plain";
                HttpContext.Current.Response.Write(e);
            }
        }

        protected abstract Stream ReadImageStream(string fileNameWithExtension);

        private Image ReadImage(string fileNameWithExtension) {
            using (Stream st = this.ReadImageStream(fileNameWithExtension)) {
                if (st != null) {
                    using (BinaryReader br = new BinaryReader(st))
                    using (MemoryStream ms = new MemoryStream()) {
                        byte[] data = br.ReadBytes((int)st.Length);
                        ms.Write(data, 0, data.Length);
                        return Image.FromStream(ms);
                    }
                } else
                    return null;
            }
        }

        private void SetGZip() {
            if (IsGZipSupported()) {
                HttpResponse response = HttpContext.Current.Response;

                string acceptEncoding = HttpContext.Current.Request.Headers["Accept-Encoding"];
                if (acceptEncoding.Contains("gzip")) {
                    response.Filter = new System.IO.Compression.GZipStream(response.Filter, System.IO.Compression.CompressionMode.Compress);
                    response.AppendHeader("Content-Encoding", "gzip");
                } else {
                    response.Filter = new System.IO.Compression.DeflateStream(response.Filter, System.IO.Compression.CompressionMode.Compress);
                    response.AppendHeader("Content-Encoding", "deflate");
                }
            }
        }

        private void SetHeadersForAll(HttpResponse response, string eTag) {
            response.Clear();
            response.Cache.SetCacheability(HttpCacheability.Public);
            response.Cache.VaryByHeaders["If-Modified-Since"] = true;
            response.Cache.VaryByHeaders["If-None-Match"] = true;
            if (this.lastModified > DateTime.MinValue)
                response.Cache.SetLastModified(this.lastModified);
            response.Cache.SetExpires(DateTime.Now.AddDays(90));
            response.Cache.SetETag(eTag);
        }

        private void SetHeadersForImage(HttpResponse response, string fileExtension) {
            response.ContentType = "image/" + fileExtension;
            response.Cache.SetAllowResponseInBrowserHistory(true);
        }

        private void SetHeadersFor404NotFound(HttpResponse response) {
            response.Clear();
            response.Status = "404 Not Found";
            response.StatusCode = 404;
            response.End();
        }

        private void SetIsNotModified(HttpResponse response) {
            response.StatusCode = 304;
            response.StatusDescription = "Not Modified";
            response.Headers.Add("Content-Length", "0");
            //response.End();
        }

        private void SetTags(string fileNameWithExtenstion, Size imageSize, bool innerSize, HttpRequest request) {
            if (HttpContext.Current.Request.RawUrl.Contains("nocache=1")) return;
            string eTag = this.GetGuid(fileNameWithExtenstion);
            if (imageSize != Size.Empty)
                eTag = eTag + imageSize.Width + '-' + imageSize.Height;
            eTag = eTag + (innerSize ? "1" : "0");
            if (this.GetWatermarks().Length > 0) {
                eTag += string.Join("-", this.GetWatermarks().Select(w => w.ToString()).ToArray()).GetMd5();
            }

            ColorMatrix matrix = this.GetColorMods();
            if (matrix != null)
                eTag += "-" + matrix.Matrix00 + "," + matrix.Matrix01 + "," + matrix.Matrix02 + "," + matrix.Matrix03 + "," + matrix.Matrix04 + "," + matrix.Matrix20 + "," + matrix.Matrix21 + "," + matrix.Matrix22 + "," + matrix.Matrix23 + "," + matrix.Matrix24 + "," + matrix.Matrix20 + "," + matrix.Matrix21 + "," + matrix.Matrix22 + "," + matrix.Matrix23 + "," + matrix.Matrix24 + "," + matrix.Matrix30 + "," + matrix.Matrix31 + "," + matrix.Matrix32 + "," + matrix.Matrix33 + "," + matrix.Matrix34 + "," + matrix.Matrix40 + "," + matrix.Matrix41 + "," + matrix.Matrix42 + "," + matrix.Matrix43 + "," + matrix.Matrix44;
            string eTagRequest = request.Headers["If-None-Match"];

            this.eTag = eTag;
            this.eTagRequest = eTagRequest;

        }

        private ImageFormat GetImageFormat(string fileExtension) {
            ImageFormat format = null;
            switch (fileExtension) {
                case "gif": format = ImageFormat.Gif; break;
                case "png": format = ImageFormat.Png; break;
                default: format = ImageFormat.Jpeg; break;
            }
            return format;
        }

        private void WriteImageToResponse(Image image, ImageFormat imageFormat, HttpResponse response) {

            long imageSizeLimit = 102400; // w kb to jest 10MB
            if (!string.IsNullOrEmpty(HttpContext.Current.Request["size"])) {
                if (!Int64.TryParse(HttpContext.Current.Request["size"], out imageSizeLimit))
                    imageSizeLimit = 102400;
            }
            imageSizeLimit = imageSizeLimit * 1024;// zamiana na bajty
            long quality = 97L;
            using (image) {
                while (true) {
                    using (MemoryStream mst = new MemoryStream()) {
                        if (image != null) {
                            EncoderParameters parameters = new EncoderParameters(1);
                            parameters.Param[0] = new EncoderParameter(System.Drawing.Imaging.Encoder.Quality, quality);
                            image.Save(mst, GetEncoder(imageFormat), parameters);
                            if (mst.Length > imageSizeLimit) {
                                mst.Dispose();
                                quality--;
                                continue;
                            }
                            mst.WriteTo(response.OutputStream);
                            mst.Dispose();
                            break;
                        }
                    }
                }
            }
            //response.End();
        }

        private ColorMatrix GetColorMods() {
            string color = HttpContext.Current.Request["color"];
            int opacity = 0;
            Int32.TryParse(HttpContext.Current.Request["opacity"], out opacity);
            if (!string.IsNullOrEmpty(color) && color.Length > 3) {
                return this.GetRequestColorMatrix(color, opacity);
            }
            return this.GetModification();
        }

        private ColorMatrix GetRequestColorMatrix(string htmlColor, int opacity) {
            Color color = System.Drawing.ColorTranslator.FromHtml(htmlColor);
            if (opacity == 0 || opacity > 255) opacity = 255;
            ColorMatrix cm = new ColorMatrix();
            cm.Matrix00 = color.R / 255f;
            cm.Matrix11 = color.G / 255f;
            cm.Matrix22 = color.B / 255f;
            cm.Matrix33 = opacity / 255f;
            cm.Matrix44 = 1;
            return cm;
        }

        protected virtual ColorMatrix GetModification() {
            return null;
        }

        private void AcceptModification(Image sourceImage, Graphics gp) {
            ColorMatrix matrix = this.GetColorMods();
            if (matrix != null) {
                Bitmap bp = new Bitmap(sourceImage);
                ImageAttributes attr = new ImageAttributes();
                Rectangle rc = new Rectangle(0, 0, sourceImage.Width, sourceImage.Height);
                ColorAdjustType cat = ColorAdjustType.Default;
                attr.SetColorMatrix(matrix, ColorMatrixFlag.Default, cat);
                gp.Clear(Color.Transparent);
                gp.DrawImage(bp, rc, 0, 0, sourceImage.Width, sourceImage.Height, GraphicsUnit.Pixel, attr);
                gp.Dispose();
            }
        }

        #endregion

    }
}